using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace cb0tServer
{
    class WebServer
    {
        private WebClientObject[] clients = new WebClientObject[300];

        private Thread thread;
        private TcpListener listener;

        private bool terminate = false;

        public void Init(int port)
        {
            this.thread = new Thread(new ParameterizedThreadStart(this.HttpThread));
            this.thread.Start(port);
        }

        public void Die()
        {
            if (this.listener != null)
            {
                this.terminate = true;

                try
                {
                    this.listener.Stop();
                }
                catch { }

                foreach (WebClientObject c in this.clients)
                    if (c.IsSet())
                        c.Disconnect();
            }
        }

        private void HttpThread(object args)
        {
            DefaultWebpage.Check();

            this.terminate = false;
            this.listener = new TcpListener(IPAddress.Any, (int)args);

            try
            {
                this.listener.Start();
            }
            catch
            {
                System.Windows.Forms.MessageBox.Show("CRITICAL ERROR: UNABLE TO BIND PORT " + ((int)args));
                return;
            }

            for (int i = 0; i < this.clients.Length; i++)
                this.clients[i] = new WebClientObject();

            while (true)
            {
                if (this.terminate) return;

                if (this.listener.Pending()) // inbound connection queued
                {
                    Socket socket = this.listener.AcceptSocket();
                    int get_id = this.NextAvailable();

                    if (get_id == -1) // highly unlikely, but currently dealing with over 300 users
                    {
                        try
                        {
                            socket.Disconnect(false); // reject user
                        }
                        catch { }
                    }
                    else // set up socket in a user slot
                    {
                        this.clients[get_id] = new WebClientObject(socket);
                    }
                }

                this.ServiceUserSockets(); // check sockets for inbound / outbound

                Thread.Sleep(30); // minimise cpu usage
            }
        }

        private void ServiceUserSockets()
        {
            int time_now = (int)cb0tServer.Helpers.UnixTime();

            foreach (WebClientObject c in this.clients)
            {
                if (c.IsSet())
                {
                    if (!c.request_obtained) // still waiting for request to be received from client
                    {
                        if ((c.spawn_time + 15) < time_now) // taking too long to get request (max 15 seconds to send request)
                        {
                            c.Disconnect();
                            continue;
                        }

                        SocketError e = SocketError.Success;
                        byte[] buffer1 = new byte[8196];
                        int byte_count = 0;

                        try
                        {
                            byte_count = c.socket.Receive(buffer1, 0, c.socket.Available, SocketFlags.None, out e);
                        }
                        catch { }

                        if (byte_count == 0) // nothing received, check client is still connected
                        {
                            if (e == SocketError.WouldBlock) // an acceptable error
                            {
                                c.receive_fails = 0;
                            }
                            else
                            {
                                if (c.receive_fails++ > 3) // client disconnected
                                {
                                    c.Disconnect();
                                    continue;
                                }
                            }
                        }
                        else // data was received, add to inbound dump
                        {
                            c.receive_fails = 0;
                            byte[] buffer2 = new byte[byte_count];
                            Array.Copy(buffer1, 0, buffer2, 0, byte_count);
                            c.AddBytes(buffer2);
                        }

                        buffer1 = c.GetBytes();

                        if (buffer1 != null) // let's see what we've got so far
                        {
                            if (buffer1.Length > 4096) // getting too big, possible malicious or badly formed http request
                            {
                                c.Disconnect();
                                continue;
                            }

                            int x = Array.IndexOf(buffer1, (byte)13); // \r
                            int y = Array.IndexOf(buffer1, (byte)10); // \n

                            if (x > -1)
                            {
                                if (y == (x + 1)) // found \r\n (http standard line termination)
                                {
                                    byte[] buffer2 = new byte[x];
                                    Array.Copy(buffer1, 0, buffer2, 0, x); // place line bytes in a byte array
                                    c.RemoveBytes(x + 2); // remove these bytes from the dump

                                    if (WSHelpers.ProcessReceivedLine(c, buffer2)) // ready to process request?
                                    {
                                        if (!WSHelpers.PopulateOutboundPackets(c)) // file not found or error
                                        {
                                            c.Disconnect();
                                            continue;
                                        }

                                        c.request_obtained = true;
                                    }
                                }
                            }
                        }
                    }
                    else // dispatch requested file
                    {
                        if (!c.SendPacket()) // response is complete
                        {
                            c.Disconnect();
                            continue;
                        }
                    }
                }
            }
        }

        private int NextAvailable()
        {
            for (int i = 0; i < this.clients.Length; i++)
                if (!this.clients[i].IsSet())
                    return i;

            return -1;
        }

    }
}
